﻿// WinDirStat - Directory Statistics
// Copyright © WinDirStat Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// at your option any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#include "stdafx.h"
#include "WinDirStat.h"
#include "SelectObject.h"
#include "Layout.h"
#include "MainFrame.h"

/////////////////////////////////////////////////////////////////////////////
// CLayoutDialogEx

IMPLEMENT_DYNCREATE(CLayoutDialogEx, CDialogEx)

BEGIN_MESSAGE_MAP(CLayoutDialogEx, CDialogEx)
    ON_WM_SIZE()
    ON_WM_GETMINMAXINFO()
    ON_WM_DESTROY()
END_MESSAGE_MAP()

BOOL CLayoutDialogEx::PreTranslateMessage(MSG* pMsg)
{
    // Check for Ctrl+C key combination
    if (pMsg->message == WM_KEYDOWN && pMsg->wParam == 'C' && 
        (GetKeyState(VK_CONTROL) & HSHELL_HIGHBIT))
    {
        // Get the mouse cursor position
        CPoint pt;
        GetCursorPos(&pt);
        ScreenToClient(&pt);
        
        // Find which child window is at this position
        if (const CWnd* pWndUnderCursor = ChildWindowFromPoint(pt, CWP_SKIPINVISIBLE);
            pWndUnderCursor != nullptr && pWndUnderCursor != this)
        {
            // Check if it's a static control
            WCHAR className[MAX_CLASS_NAME]{};
            ::GetClassName(pWndUnderCursor->GetSafeHwnd(), className, std::size(className));
            if (_wcsicmp(className, WC_STATIC) == 0)
            {
                // Get the text from the static control
                CString text;
                pWndUnderCursor->GetWindowText(text);
                if (!text.IsEmpty())
                {
                    CMainFrame::Get()->CopyToClipboard(text.GetString());
                    return TRUE; // Message handled
                }
            }
        }
    }

    return CDialogEx::PreTranslateMessage(pMsg);
}

void CLayoutDialogEx::OnSize(UINT nType, int cx, int cy)
{
    CDialogEx::OnSize(nType, cx, cy);
    m_Layout.OnSize();
}

void CLayoutDialogEx::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
{
    m_Layout.OnGetMinMaxInfo(lpMMI);
    CDialogEx::OnGetMinMaxInfo(lpMMI);
}

void CLayoutDialogEx::OnDestroy()
{
    m_Layout.OnDestroy();
    CDialogEx::OnDestroy();
}

/////////////////////////////////////////////////////////////////////////////
// CLayout

CLayout::CLayout(CWnd* dialog, RECT* placement)
    : m_Wp(placement), m_Dialog(dialog), m_OriginalDialogSize(0, 0)
{
    ASSERT(dialog != nullptr);
}

int CLayout::AddControl(CWnd* control, double movex, double movey, double stretchx, double stretchy)
{
    m_Control.emplace_back(control, movex, movey, stretchx, stretchy);
    return static_cast<int>(m_Control.size() - 1);
}

void CLayout::AddControl(const UINT id, const double movex, const double movey, const double stretchx, const double stretchy)
{
    AddControl(m_Dialog->GetDlgItem(id), movex, movey, stretchx, stretchy);
}

void CLayout::OnInitDialog(const bool centerWindow)
{
    m_Dialog->SetIcon(CDirStatApp::Get()->LoadIcon(IDR_MAINFRAME), false);

    CRect rcDialog;
    m_Dialog->GetWindowRect(rcDialog);
    m_OriginalDialogSize = rcDialog.Size();

    for (auto& info : m_Control)
    {
        CRect rc;
        info.control->GetWindowRect(rc);
        m_Dialog->ScreenToClient(rc);
        info.originalRectangle = rc;
    }

    // Create size gripper
    CRect sg;
    m_Dialog->GetClientRect(sg);
    sg.left = sg.right - m_SizeGripper.m_Width;
    sg.top = sg.bottom - m_SizeGripper.m_Width;
    m_SizeGripper.Create(m_Dialog, sg);

    const int i = AddControl(&m_SizeGripper, 1, 1, 0, 0);
    m_Control[i].originalRectangle = sg;

    m_Dialog->MoveWindow(m_Wp);
    if (centerWindow)
    {
        m_Dialog->CenterWindow();
    }
}

void CLayout::OnDestroy() const
{
    m_Dialog->GetWindowRect(m_Wp);
}

void CLayout::OnSize()
{
    CRect wrc;
    m_Dialog->GetWindowRect(wrc);
    const CSize diff = wrc.Size() - m_OriginalDialogSize;

    CPositioner pos(static_cast<int>(m_Control.size()));

    for (const auto& [control, movex, movey, stretchx, stretchy, originalRectangle] : m_Control)
    {
        CRect rc = originalRectangle;

        rc.OffsetRect(static_cast<int>(diff.cx * movex), static_cast<int>(diff.cy * movey));
        rc.right += static_cast<int>(diff.cx * stretchx);
        rc.bottom += static_cast<int>(diff.cy * stretchy);

        pos.SetWindowPos(*control, rc.left, rc.top, rc.Width(), rc.Height(), SWP_NOOWNERZORDER | SWP_NOZORDER);
    }

    m_Dialog->Invalidate();
}

void CLayout::OnGetMinMaxInfo(MINMAXINFO* mmi)
{
    if (m_OriginalDialogSize.cx > 0) // Check if initialized
    {
        mmi->ptMinTrackSize = { m_OriginalDialogSize.cx, m_OriginalDialogSize.cy };
    }
}

/////////////////////////////////////////////////////////////////////////////

void CLayout::CSizeGripper::Create(CWnd* parent, const CRect rc)
{
    VERIFY(CWnd::Create(AfxRegisterWndClass(0,
        CDirStatApp::Get()->LoadStandardCursor(IDC_ARROW), nullptr, nullptr),
        wds::strEmpty, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, rc, parent, IDC_SIZEGRIPPER));
}

BEGIN_MESSAGE_MAP(CLayout::CSizeGripper, CWnd)
    ON_WM_PAINT()
    ON_WM_NCHITTEST()
    ON_WM_ERASEBKGND()
END_MESSAGE_MAP()

BOOL CLayout::CSizeGripper::OnEraseBkgnd(CDC* pDC)
{
    CRect rc;
    GetClientRect(rc);
    pDC->FillSolidRect(rc, DarkMode::WdsSysColor(CTLCOLOR_DLG));
    return TRUE;
}

void CLayout::CSizeGripper::OnPaint()
{
    CPaintDC dc(this);

    CRect rc;
    GetClientRect(rc);

    ASSERT(rc.Width() == m_Width);
    ASSERT(rc.Height() == m_Width);

    // Draw three diagonal shadow lines
    for (int offset : {1, 5, 9})
    {
        DrawShadowLine(&dc, { offset, m_Width }, { m_Width, offset });
    }
}

void CLayout::CSizeGripper::DrawShadowLine(CDC* pdc, CPoint start, CPoint end)
{
    // Draw highlight line
    {
        CPen lightPen(PS_SOLID, 1, DarkMode::WdsSysColor(COLOR_3DHIGHLIGHT));
        const CSelectObject sopen(pdc, &lightPen);
        pdc->MoveTo(start);
        pdc->LineTo(end);
    }

    // Draw shadow lines (2 pixels for depth effect)
    CPen darkPen(PS_SOLID, 1, DarkMode::WdsSysColor(COLOR_3DSHADOW));
    const CSelectObject sopen(pdc, &darkPen);

    for (int i = 1; i <= 2; ++i)
    {
        pdc->MoveTo(start.x + i, start.y + i);
        pdc->LineTo(end.x + i, end.y + i);
    }
}

LRESULT CLayout::CSizeGripper::OnNcHitTest(CPoint point)
{
    ScreenToClient(&point);
    return (point.x + point.y >= m_Width) ? HTBOTTOMRIGHT : 0;
}

CLayout::CPositioner::CPositioner(const int nNumWindows)
    : m_Wdp(BeginDeferWindowPos(nNumWindows))
{
}

CLayout::CPositioner::~CPositioner()
{
    if (m_Wdp != nullptr)
    {
        EndDeferWindowPos(m_Wdp);
    }
}

void CLayout::CPositioner::SetWindowPos(HWND hWnd, const int x, const int y, const int cx, const int cy, const UINT uFlags)
{
    m_Wdp = DeferWindowPos(m_Wdp, hWnd, nullptr, x, y, cx, cy, uFlags | SWP_NOZORDER);
}
